package top.lingkang.finalsecurity.jakarta;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import top.lingkang.finalsecurity.jakarta.http.FinalRequestContext;
import top.lingkang.finalsecurity.common.base.FinalAuth;
import top.lingkang.finalsecurity.common.base.FinalHttpProperties;
import top.lingkang.finalsecurity.common.error.FinalBaseException;
import top.lingkang.finalsecurity.common.error.FinalNotLoginException;
import top.lingkang.finalsecurity.common.error.FinalPermissionException;
import top.lingkang.finalsecurity.common.utils.AuthUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 应该只初始化一次此配置
 * <pre>
 * {@code
 * @EnableFinalSecurityAnnotation // 开启 FinalSecurity AOP鉴权注解
 * @Configuration
 * public class Myconfig extends FinalSecurityConfiguration {
 *     @Override
 *     protected void config(FinalHttpProperties properties) {
 *         // 对项目进行配置
 *         properties.checkAuthorize()
 *                 .pathMatchers("/user").hasAnyRole("user", "vip1") // 有其中任意角色就能访问
 *                 .pathMatchers("/vip/**").hasAllRole("user", "vip1");// 必须同时有所有角色才能访问
 *
 *         // 排除鉴权路径匹配, 匹配优先级别：排除路径 > checkAuthorize > 注解
 *         properties.setExcludePath("/login", "/logout", "/vip/total", "/vip/user/**", "/**.js", "/**.css");
 *     }
 * }
 * }
 * </pre>
 * 过滤配置类（核心）
 * 匹配优先级别：排除 > 鉴权
 *
 * @author lingkang
 * Created by 2022/2/11
 * @since 3.0.0
 */
public class FinalSecurityConfiguration implements Filter {
    private FinalHttpProperties properties = new FinalHttpProperties();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        FinalRequestContext.setRequest(request);
        try {
            String path = request.getServletPath();
            // 缓存相关
            if (properties.getCheckPathCache().getExcludePath().contains(path)) {
                chain.doFilter(servletRequest, servletResponse);
                return;
            } else if (properties.getCheckPathCache().getAuths().containsKey(path)) {
                FinalAuth[] finalAuths = properties.getCheckPathCache().getAuths().get(path);
                for (FinalAuth auth : finalAuths) {
                    auth.check(new FinalSessionObjectJakarta(request.getSession()));
                }

                chain.doFilter(servletRequest, servletResponse);
                return;
            }

            // 排除
            for (String url : properties.getExcludePath()) {
                if (AuthUtils.matcher(url, path)) {
                    properties.getCheckPathCache().getExcludePath().add(path);// 添加缓存
                    chain.doFilter(servletRequest, servletResponse);
                    return;
                }
            }

            // 检查角色
            HashMap<String, FinalAuth> checkAuth = properties.getCheckAuthorize().getAuthorize();
            List<FinalAuth> auths = new ArrayList<>();
            for (Map.Entry<String, FinalAuth> entry : checkAuth.entrySet()) {
                if (AuthUtils.matcher(entry.getKey(), path)) {
                    auths.add(entry.getValue());
                }
            }

            // cache
            properties.getCheckPathCache().getAuths().put(path, AuthUtils.AllToOne(auths.toArray(new FinalAuth[auths.size()])));

            // 执行检查
            for (FinalAuth auth : auths) {
                auth.check(new FinalSessionObjectJakarta(request.getSession()));
            }

            //放行
            chain.doFilter(servletRequest, servletResponse);
        } catch (Exception e) {
            if (FinalBaseException.class.isAssignableFrom(e.getClass()) || FinalBaseException.class.isAssignableFrom(e.getCause().getClass())) {
                HttpServletResponse response = (HttpServletResponse) servletResponse;
                if (e instanceof FinalPermissionException) {
                    properties.getExceptionHandler().permissionException(e, request, response);
                } else if (e.getCause() instanceof FinalPermissionException)
                    properties.getExceptionHandler().permissionException(e.getCause(), request, response);
                else if (e instanceof FinalNotLoginException) {
                    properties.getExceptionHandler().notLoginException(e, request, response);
                } else if (e.getCause() instanceof FinalNotLoginException)
                    properties.getExceptionHandler().notLoginException(e.getCause(), request, response);
                else
                    properties.getExceptionHandler().exception(e, request, response);
            } else {
                throw e;
            }
        } finally {
            FinalRequestContext.removeRequest();
        }
    }

    protected void config(FinalHttpProperties properties) {
        this.properties = properties;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        if (properties.getExceptionHandler() == null)
            properties.setExceptionHandler(new DefaultFinalExceptionHandler());

        config(properties);
    }

    public FinalHttpProperties getProperties() {
        return properties;
    }

    /**
     * 清理缓存
     */
    public void clearCache() {
        properties.getCheckPathCache().getExcludePath().clear();
        properties.getCheckPathCache().getAuths().clear();
    }

}
